import { ApiMsgEnum, InputTypeEnum } from "./Enum";
import { strdecode, strencode, toFixed } from "./Utils";

const encodeActorMove = (input: any, view: DataView, index: number) => {
  view.setUint8(index++, input.type)
  view.setUint8(index++, input.id)
  view.setFloat32(index, input.direction.x)
  index += 4
  view.setFloat32(index, input.direction.y)
  index += 4
  view.setFloat32(index, input.dt)
  index += 4
}

const encodeWeaponShoot = (input: any, view: DataView, index: number) => {
  view.setUint8(index++, input.type)
  view.setUint8(index++, input.owner)
  view.setFloat32(index, input.position.x)
  index += 4
  view.setFloat32(index, input.position.y)
  index += 4
  view.setFloat32(index, input.direction.x)
  index += 4
  view.setFloat32(index, input.direction.y)
  index += 4
}

export const encodeTimePast = (input: any, view: DataView, index: number) => {
  view.setUint8(index++, input.type)
  view.setFloat32(index, input.dt)
  index += 4
}

export const binaryEncode = (name: ApiMsgEnum, data: any): DataView => {
  if (name === ApiMsgEnum.MsgClientSync) {
    //name 1字节 + frameId 4字节 + 数据长度 n 字节
    const { frameId, input } = data
    if (input.type === InputTypeEnum.ActorMove) {
      let index = 0
      const ab = new ArrayBuffer(1 + 4 + 14)
      const view = new DataView(ab)
      view.setUint8(index++, name)
      view.setUint32(index, frameId)
      index += 4
      encodeActorMove(input, view, index)
      return view
    } else if (input.type === InputTypeEnum.WeaponShoot) {
      let index = 0
      const ab = new ArrayBuffer(1 + 4 + 18)
      const view = new DataView(ab)
      view.setUint8(index++, name)
      view.setUint32(index, frameId)
      index += 4
      encodeWeaponShoot(input, view, index)
      return view
    } else {
      let index = 0
      const ab = new ArrayBuffer(1 + 4 + 5)
      const view = new DataView(ab)
      view.setUint8(index++, name)
      view.setUint32(index, frameId)
      index += 4
      encodeTimePast(input, view, index)
      return view
    }
  } else if (name === ApiMsgEnum.MsgServerSync) {
    const { lastFrameId, inputs } = data
    let total = 0
    for (const input of inputs) {
      if (input.type === InputTypeEnum.ActorMove) {
        total += 14
      } else if (input.type === InputTypeEnum.WeaponShoot) {
        total += 18
      } else {
        total += 5
      }
    }
    //name 1字节 + lastFrameId 4字节 + 数组长度 1字节 + 数据长度 n 字节
    const ab = new ArrayBuffer(1 + 4 + 1 + total)
    const view = new DataView(ab)
    let index = 0
    view.setUint8(index++, name)
    view.setUint32(index, lastFrameId)
    index += 4
    view.setUint8(index++, inputs.length)
    for (const input of inputs) {
      if (input.type === InputTypeEnum.ActorMove) {
        encodeActorMove(input, view, index)
        index += 14
      } else if (input.type === InputTypeEnum.WeaponShoot) {
        encodeWeaponShoot(input, view, index)
        index += 18
      } else {
        encodeTimePast(input, view, index)
        index += 5
      }
    }
    return view
  } else {
    let index = 0
    const str = JSON.stringify(data)
    const ta = strencode(str)
    const ab = new ArrayBuffer(ta.length + 1)
    const view = new DataView(ab)
    view.setUint8(index++, name)
    for (let i = 0; i < ta.length; i++) {
      view.setUint8(index++, ta[i])
    }
    return view
  }
}

const decodeActorMove = (view: DataView, index: number) => {
  const id = view.getUint8(index++)
  const directionX = toFixed(view.getFloat32(index))
  index += 4
  const directionY = toFixed(view.getFloat32(index))
  index += 4
  const dt = toFixed(view.getFloat32(index))
  index += 4
  const input = {
    type: InputTypeEnum.ActorMove,
    id,
    direction: {
      x: directionX,
      y: directionY,
    },
    dt
  }

  return input
}

const decodeWeaponShoot = (view: DataView, index: number) => {
  const owner = view.getUint8(index++)
  const positionX = toFixed(view.getFloat32(index))
  index += 4
  const positionY = toFixed(view.getFloat32(index))
  index += 4
  const directionX = toFixed(view.getFloat32(index))
  index += 4
  const directionY = toFixed(view.getFloat32(index))
  index += 4
  const input = {
    type: InputTypeEnum.WeaponShoot,
    owner,
    position: {
      x: positionX,
      y: positionY,
    },
    direction: {
      x: directionX,
      y: directionY,
    },
  }
  return input
}

const decodeTimePast = (view: DataView, index: number) => {
  const dt = toFixed(view.getFloat32(index))
  index += 4
  const input = {
    type: InputTypeEnum.TimePast,
    dt,
  }
  return input
}

export const binaryDecode = (buffer: ArrayBuffer) => {
  let index = 0
  const view = new DataView(buffer)
  const name = view.getUint8(index++)

  if (name === ApiMsgEnum.MsgClientSync) {
    const frameId = view.getUint32(index)
    index += 4
    const inputType = view.getUint8(index++)
    if (inputType === InputTypeEnum.ActorMove) {
      const input = decodeActorMove(view, index)
      return {
        name,
        data: {
          frameId,
          input
        }
      }
    } else if (inputType === InputTypeEnum.WeaponShoot) {
      const input = decodeWeaponShoot(view, index)
      return {
        name,
        data: {
          frameId,
          input
        }
      }
    } else {
      const input = decodeTimePast(view, index)
      return {
        name,
        data: {
          frameId,
          input
        }
      }
    }
  } else if (name === ApiMsgEnum.MsgServerSync) {
    const lastFrameId = view.getUint32(index)
    index += 4
    const len = view.getUint8(index++)
    const inputs = []
    for (let i = 0; i < len; i++) {
      const inputType = view.getUint8(index++)
      if (inputType === InputTypeEnum.ActorMove) {
        inputs.push(decodeActorMove(view, index))
        index += 13
      } else if (inputType === InputTypeEnum.WeaponShoot) {
        inputs.push(decodeWeaponShoot(view, index))
        index += 17
      } else {
        inputs.push(decodeTimePast(view, index))
        index += 4
      }
    }
    return {
      name: ApiMsgEnum.MsgServerSync,
      data: {
        lastFrameId,
        inputs
      }
    }
  } else {
    return {
      name: name,
      data: JSON.parse(strdecode(new Uint8Array(buffer.slice(1))))
    }
  }
}